# 一些需要注意的js输出问题整理

# 1、js运行之前,会把带有var和function关键字的事先声明,但不会赋值

alert(a) //function a(){alert(10)}
a();//执行alert(10)
var a=3;
function a(){
    alert(10)
}   
alert(a) //3
a=6;
a(); //a已经不是一个函数了报错
1
2
3
4
5
6
7
8
9
alert(a) //undefined
a(); // 报错 a 不是一个函数,函数表达式函数必须在表达式之后调用
var a=3;
var a=function(){
    alert(10)
}   
alert(a)
a=6;
a(); 
1
2
3
4
5
6
7
8
9

考点:第一变量和函数声明提前,第二函数声明优先于变量声明!

# 2、下面代码输出什么?

var a=0;
var c=0;
function aa(a){
    alert(a)
    var a=3
    c = 10;
}
aa(5) // 5
alert(a) //这里的a未受到函数体内a变量覆盖
alert(c) // 10
1
2
3
4
5
6
7
8
9
10
var a=0;
function aa(a){
    alert(a)
    a=3
}
aa(5)
alert(a)
//5,0   在函数体内,执行alert(a)和a=3,修改的的并不是全局变量a,而是参数a
1
2
3
4
5
6
7
8

考点: 函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找;这里在函数体内,参数a的优先级高于变量a

# 3、严格模式下,以下程序的输出是什么:

(function(){
  var a = b = 3;
})();
console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));
1
2
3
4
5

答案: a defined? false,b defined? true

理解这道题的核心在于如何理解var a = b = 3这句话,实际上这句话等于

var a; 
b = 3;
a = 3;
1
2
3

这样子,实际上,b是声明在了全局变量中(编译器在预编译帮你声明了,然而在严格模式下是不行的) a是局部变量,所以在函数之外是没有定义的。

# 4、数组的filter,以下输出结果是什么:

var arr = [1,2,3];
arr[10] = 9;
arr.filter((item)=> {
    return item === undefined
})

//答案
[]
1
2
3
4
5
6
7
8

解析: 是的,答案的确是[],不是[undefined x 7]。当数组中都是undefined时,数组就是空,或者说[empty x 7] === []。

# 5、写一个sum方法,可以实现以下两种调用方式

console.log(sum(2,3)) //5
console.log(sum(2)(3)) //5
1
2

答案:

//方法1
var sum = function(x,y) {
    if(y === undefined) {
        return function(y) {
            return x + y;
        }
    }else {
        return x + y;
    }
}

//方法2
var sum = function(x){
    if( arguments.length === 1) {
        return function (y) {
            return x + y;
        }
    } else {
        console.log('here');
        return arguments[0] + arguments[1];
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 6、+ - 运算符之惑:

console.log(1 +  "2" + "2"); //"122"
console.log(1 +  +"2" + "2"); //"32"
console.log(1 +  -"1" + "2"); // "02"
console.log(+"1" +  "1" + "2");//"112" 
console.log( "A" - "B" + "2"); "NAN2"
console.log( "A" - "B" + 2); "NAN"
1
2
3
4
5
6

核心是以下几点:

  • "- +"会隐式转换为Number类型
  • 当"+"作为运算符出现在String类型前时,会认为需要字符串拼接,因此会隐式转换为String
  • Number包含一个特殊的类型NaN,当对非数字进行Number转换时,会变为这个。

# 7、堆栈溢出之谜

下面的代码将会造成栈溢出,请问如何优化,不改变原有逻辑:

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        nextListItem();
    }
};
1
2
3
4
5
6
7
8
9
10

答案:

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        setTimeout(nextListItem,0)
	}
};
1
2
3
4
5
6
7
8

首先必须搞清楚,堆栈溢出的原因:

原因是每次执行代码时,都会分配一定尺寸的栈空间(Windows系统中为1M),每次方法调用时都会在栈里储存一定信息(如参数、局部变量、返回值等等),这些信息再少也会占用一定空间,成千上万个此类空间累积起来,自然就超过线程的栈空间了。那么如何解决此类问题?

这里介绍两个思路解决此问题: 1、异步,2、闭包

显然,这里就是使用的第一种方法,闭包。为什么使用setTimeout就可以解决问题?我们看下与没用之前的差别。如果没有使用setTimeout,那么函数将在大数据前不断的回调,直到最后走到重点,最初的函数才运行结束,释放内存。 但是如果使用了setTimeout,我们知道它是异步的,即使设置了时间为0,它也允许先执行下面的内容,可以释放堆栈,从而避免堆栈溢出的问题。 换言之,加了setTimeout,nextListItem函数被压入事件队列,函数可以退出,因此每次会清空调用堆栈。

# 8、你真的懂对象(Object)的key吗?

var a={},
    b={key:'b'},
    c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]); //456
1
2
3
4
5
6
7
8

原因是什么呢? 这里了解ES6新的数据类型 map 的应该就会意识到了,没错,对象的 key 值是只允许 String 类型的,这也是为什么引入了 map 数据类型了。 好了,那如果把一个对象作为 key 值,就会调用 toString 方法了。

Object.prototype.toString(obj) 会得到什么呢?没错`[object Object]。 那所以

a[b] ==> a["[object Object"] = 123;
a[c] ==> a["[object Object"] = 456;
1
2

# 9、回文判断

请做一个回文判断的函数,判断是否是回文

这里主要考虑了一个健壮性的问题,多了一个正则来检测:

function check(str) {
    str = str.replace(/\W/g,'').toLowerCase();
    return str === str.split('').reverse().join('')
}
1
2
3
4

# 10、下面程序的输出结果是?

var length = 10;
function fn() {
	console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1); // 10  2
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这个我做错在第二个输出上,其实对this了解后就知道,第一个输出10应该是很显然的:虽然在程序执行时,使用了obj.method方法,让this指向了obj,但是真正的函数执行在函数体内部,也即当fn()执行的时候,this是指向window的,所以第一次执行结果是10

那么这里第二次执行arguments[0]为什么结果是2?

分析下在method(fn,1)执行时,经历了什么: 首先两个参数fn和1会被放入arguments中,在arguments中第一个参数就是我们传入的函数;接下来fn执行,此时this没有绑定因此指向window,输出10。 然而到了arguments0这一句,相当于把arguments[0]中的第一个参数拿来执行, 效果如下:

arguments[0]()  //执行,等同于下面的
arguments.0() //当然这句话是不合法的,但是这样我们可以更清楚知道,this是指向arguments实例本身
1
2

arguments.length就是它本身的长度(arguments是一个类数组,具有length属性),因此输出2

# 11、try..catch程序的输出结果

(function () {
    try {
        throw new Error();
    } catch (x) {
        var x = 1, y = 2;
        console.log(x);
    }
    console.log(x);
    console.log(y);
})();
1
2
3
4
5
6
7
8
9
10

输出结果:

1
undefined
2
1
2
3

我们都知道var是在预编译阶段会有一个变量提升,这种类型很容易解决,但是当遇到在catch(x)中与已有变量重名的情况,一定要区分两者之间的关系。

用变量提升的方法,把程序重写并分析如下:

(function () {
    var x,y;  // 外部变量提升
    try {
        throw new Error();
    } catch (x/* 内部的x */) {
		x = 1; //内部的x,和上面声明的x不是一回事!!
        y = 2; //内部没有声明,作用域链向上找,外面的y
        console.log(x); //当然是1
    }
    console.log(x);  //只声明,未赋值,undefined
    console.log(y);  //就是2了
})();
1
2
3
4
5
6
7
8
9
10
11
12

这样子就很清晰,之后注意预编译的过程,把变量和函数定义进行提升后,进行分析,会清楚很多

# 12、下面程序的输出

var x = 21;
var girl = function () {
    console.log(x);
    var x = 20;
};
girl (); // undefined
1
2
3
4
5
6

函数内部变量提升。 相当于:

var x = 21;
var girl = function() {
    var x;
    console.log(x); // undefined
    x = 20;
}
}
1
2
3
4
5
6
7

# 13、 运算符考点: 下面程序输出是什么?

console.log(1 < 2 < 3);
console.log(3 > 2 > 1);

//true
//flase
1
2
3
4
5

核心在于 js 怎么去解析 < 和 > 运算符。 在JS中,这种运算符是从左向右运算的,所以 3>2>1 就被转换成了 true>1,而 true 的值是 1,接着比较 1>1 就返回 false 了。

# 14、parseInt (val, radix) :两个参数,val 值,radix 基数(就是多少进制转换)

["1", "2", "3"].map(parseInt)
// 答案:[1, NaN, NaN]
// 其实等于 
['1', '2', '3'].map((item, index) => {
	return parseInt(item, index)
})
1
2
3
4
5
6
  1. parseInt('1', 0); // radix为0时,使用默认的10进制。
  2. parseInt('2', 1); // radix值在2-36,无法解析,返回NaN
  3. parseInt('3', 2); // 基数为2,2进制数表示的数中,最大值小于3,无法解析,返回NaN

# 15、IEEE 754标准中的浮点数并不能精确地表达小数

var two   = 0.2
var one   = 0.1
var eight = 0.8
var six   = 0.6
[two - one == one, eight - six == two]
//答案:[true, false]
1
2
3
4
5
6
// 巩固:
var two   = 0.2;
var one   = 0.1;
var eight = 0.8;
var six   = 0.6;
(eight - six).toFixed(4) == two //true
1
2
3
4
5
6

# 16、下面代码的输出是什么?

const shape = {
  radius: 10,
  diameter() {
    return this.radius * 2;
  },
  perimeter: () => 2 * Math.PI * this.radius
};

shape.diameter();
shape.perimeter();
1
2
3
4
5
6
7
8
9
10

答案: 20 and NaN

请注意,diameter是普通函数,而perimeter是箭头函数。

对于箭头函数,this关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用perimeter时,它不是指向shape对象,而是指其定义时的环境(window)。没有值radius属性,返回undefined。

# 17、哪个选项是不正确的?

const bird = {
  size: "small"
};

const mouse = {
  name: "Mickey",
  small: true
};
1
2
3
4
5
6
7
8
  • A: mouse.bird.size
  • B: mouse[bird.size]
  • C: mouse[bird["size"]]
  • D: All of them are valid

答案: A

在JavaScript中,所有对象键都是字符串(除了Symbol)。尽管有时我们可能不会给定字符串类型,但它们总是被转换为字符串。

JavaScript解释语句。当我们使用方括号表示法时,它会看到第一个左括号[,然后继续,直到找到右括号]。只有在那个时候,它才会对这个语句求值。

mouse [bird.size]:首先它会对bird.size求值,得到small。 mouse [“small”]返回true。

但是,使用点表示法,这不会发生。 mouse没有名为bird的键,这意味着mouse.bird是undefined。 然后,我们使用点符号来询问size:mouse.bird.size。 由于mouse.bird是undefined,我们实际上是在询问undefined.size。 这是无效的,并将抛出Cannot read property "size" of undefined。

# 18、下面代码的输出是什么?

let a = 3;
let b = new Number(3);
let c = 3;

console.log(a == b);
console.log(a === b);
console.log(b === c);
1
2
3
4
5
6
7

答案:true false false

new Number()是一个内置的函数构造函数。 虽然它看起来像一个数字,但它并不是一个真正的数字:它有一堆额外的功能,是一个对象。

当我们使用==运算符时,它只检查它是否具有相同的值。 他们都有3的值,所以它返回true。

译者注:== 会引发隐式类型转换,右侧的对象类型会自动拆箱为 Number 类型。

然而,当我们使用===操作符时,类型和值都需要相等,new Number() 不是一个数字,是一个对象类型。两者都返回 false。

# 19、下面代码的输出是什么?

class Chameleon {
  static colorChange(newColor) {
    this.newColor = newColor;
  }

  constructor({ newColor = "green" } = {}) {
    this.newColor = newColor;
  }
}

const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");
1
2
3
4
5
6
7
8
9
10
11
12

答案: TypeError

colorChange 方法是静态的。 静态方法仅在创建它们的构造函数中存在,并且不能传递给任何子级。 由于freddie是一个子级对象,函数不会传递,所以在 freddie 实例上不存在 freddie 方法:抛出 TypeError。

# 20、当我们这样做时会发生什么?

function bark() {
  console.log("Woof!");
}

bark.animal = "dog";
1
2
3
4
5
  • A: Nothing, this is totally fine!
  • B: SyntaxError. You cannot add properties to a function this way.
  • C: undefined
  • D: ReferenceError

答案: A

这在JavaScript中是可能的,因为函数也是对象!(原始类型之外的所有东西都是对象)

函数是一种特殊类型的对象。您自己编写的代码并不是实际的函数。 该函数是具有属性的对象,此属性是可调用的。

# 21、下面代码的输出是什么?

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const member = new Person("Lydia", "Hallie");
Person.getFullName = () => this.firstName + this.lastName;

console.log(member.getFullName());
1
2
3
4
5
6
7
8
9
  • A: TypeError
  • B: SyntaxError
  • C: Lydia Hallie
  • D: undefined undefined

答案: A

您不能像使用常规对象那样向构造函数添加属性。 如果要一次向所有对象添加功能,则必须使用原型。 所以在这种情况下应该这样写:

Person.prototype.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
}
1
2
3

这样会使 member.getFullName() 是可用的,为什么样做是对的? 假设我们将此方法添加到构造函数本身。 也许不是每个 Person 实例都需要这种方法。这会浪费大量内存空间,因为它们仍然具有该属性,这占用了每个实例的内存空间。 相反,如果我们只将它添加到原型中,我们只需将它放在内存中的一个位置,但它们都可以访问它!

# 22、所有对象都有原型吗?

不是的,础对象指原型链终点的对象。基础对象的原型是null。

# 23、下面代码的输出是什么?

function getPersonInfo(one, two, three) {
  console.log(one);
  console.log(two);
  console.log(three);
}

const person = "Lydia";
const age = 21;

getPersonInfo`${person} is ${age} years old`;
1
2
3
4
5
6
7
8
9
10
  • A: Lydia 21 ["", "is", "years old"]
  • B: ["", "is", "years old"] Lydia 21
  • C: Lydia ["", "is", "years old"] 21

答案: B

如果使用标记的模板字符串,则第一个参数的值始终是字符串值的数组。 其余参数获取传递到模板字符串中的表达式的值!

# 24、下面代码的输出是什么?

const sum = eval("10*10+5");
1
  • A: 105
  • B: "105"
  • C: TypeError
  • D: "10*10+5"

答案: A

eval会为字符串传递的代码求值。 如果它是一个表达式,就像在这种情况下一样,它会计算表达式。 表达式为10 * 10 + 5计算得到105。

# 25、下面代码的输出是什么?

const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);

obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);
1
2
3
4
5
6
7
  • A: false true false true
  • B: false true true true
  • C: true true false true
  • D: true true true true

答案: C

所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键。 这就是为什么obj.hasOwnProperty('1')也返回true。

上面的说法不适用于Set。 在我们的Set中没有“1”:set.has('1')返回false。 它有数字类型1,set.has(1)返回true。

# 26、下面这些值哪些是假值?

0;
new Number(0);
("");
(" ");
new Boolean(false);
undefined;
1
2
3
4
5
6
  • A: 0, '', undefined
  • B: 0, new Number(0), '', new Boolean(false), undefined
  • C: 0, '', new Boolean(false), undefined
  • D: 所有都是假值

答案: A

JavaScript中只有6个假值:

  • undefined
  • null
  • NaN
  • 0
  • '' (empty string)
  • false

函数构造函数,如 new Number 和 new Boolean 都是真值。